package main

import (
	"fmt"
	"sync"
)

type Fetcher interface {
	Fetch(url string) (body string, urls []string, err error)
}

type fakeFetcher map[string]*fakeResult

type fakeResult struct {
	body string
	urls []string
}

func (f fakeFetcher) Fetch(url string) (string, []string, error) {
	if res, ok := f[url]; ok {
		return res.body, res.urls, nil
	}
	return "", nil, fmt.Errorf("not found: %s", url)
}

var fetcher = fakeFetcher{
	"https://golang.org/": &fakeResult{
		body: "The Go Programming Language",
		urls: []string{
			"https://golang.org/pkg/",
			"https://golang.org/cmd/",
		},
	},
	"https://golang.org/pkg/": &fakeResult{
		body: "Packages",
		urls: []string{
			"https://golang.org/",
			"https://golang.org/cmd/",
			"https://golang.org/pkg/fmt/",
			"https://golang.org/pkg/os/",
		},
	},
	"https://golang.org/pkg/fmt/": &fakeResult{
		body: "Package fmt",
		urls: []string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
	"https://golang.org/pkg/os/": &fakeResult{
		body: "Package os",
		urls: []string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
}

type SafeCrawler struct {
	crawled map[string]bool
	mux     sync.Mutex
}

func (c *SafeCrawler) Crawl(url string, depth int, fetcher Fetcher, w *sync.WaitGroup) {
	//debug.PrintStack()

	//c.mux.Lock()
	//defer c.mux.Unlock()

	defer w.Done()

	if depth <= 0 {
		return
	}

	c.mux.Lock()
	_, ok := c.crawled[url]
	c.mux.Unlock()

	if ok {
		return
	}

	body, urls, err := fetcher.Fetch(url)

	c.mux.Lock()
	c.crawled[url] = err == nil
	c.mux.Unlock()

	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("found: %s %q\n", url, body)
	for _, u := range urls {
		w.Add(1)
		go c.Crawl(u, depth-1, fetcher, w)
	}
	return
}

// https://stackoverflow.com/questions/27103161/recursive-goroutines-what-is-the-neatest-way-to-tell-go-to-stop-reading-from-ch
// 我用的是以下这个方法
// https://stackoverflow.com/questions/39554252/golang-recursive-function-calling-itself-as-a-goroutine-doesnt-work-as-expected
// https://stackoverflow.com/questions/39554252/golang-recursive-function-calling-itself-as-a-goroutine-doesnt-work-as-expected#comment80914362_39554289
func main() {
	w := &sync.WaitGroup{}
	c := SafeCrawler{crawled: make(map[string]bool)}
	w.Add(1)
	go c.Crawl("https://golang.org/", 4, fetcher, w)
	w.Wait()
}
